package org.example;

import lombok.Data;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.Signature;
import org.aspectj.lang.annotation.*;
import org.aspectj.lang.reflect.SourceLocation;
import org.springframework.cglib.proxy.Enhancer;
import org.springframework.cglib.proxy.MethodInterceptor;
import org.springframework.cglib.proxy.MethodProxy;

import java.lang.annotation.Annotation;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.*;

/**
 * 继承于自定义的 IOC 容器
 * 实现 scanAspectBean() 查找组件中的切面类 => @Aspect 注解
 * 实现 createAspect() 统计切点与增强 Pointcut + Advice, 封装切面信息 AspectDefinition 对象, 保存切面集合 aspects 中
 * 实现 scanProxyBean() 扫描所有的组件, 查看是否包含被切点拦截的方法, 存入 joinPoints(Method+Bean+Aspect) 和 proxyBeans 集合
 * 实现 createProxyBean() 如果组件包含被切点拦截的方法, 那么生成动态代理对象替换原有的组件
 */
public class MyAnnotationConfigApplicationContextWithAOP extends MyAnnotationConfigApplicationContext {

    // 切面集合
    protected Map<String,AspectDefinition> aspectDefinitions = new HashMap<>();

    public MyAnnotationConfigApplicationContextWithAOP(Class configClass) {
        super(configClass);
        try{
            // 扫描切面类
            scanAspectBean();
            // 扫描IOC, 判断哪些Bean需要代理
            scanProxyBean();
        }catch (Exception e){
            throw new RuntimeException(e);
        }
    }

    void scanAspectBean() throws Exception {
        for (BeanDefinition bd : beanDefinitionMap.values()) {
            if (bd.getBeanClass().isAnnotationPresent(Aspect.class)) {
                System.out.println("切面类: " + bd.getBeanClassName());
                createAspect(bd);
            }
        }
        aspectDefinitions.values().forEach(System.out::println);
    }

    private void createAspect(BeanDefinition bd) throws Exception {
        // 遍历所有的增强方法, 提取切点表达式
        for (Method method : bd.getBeanClass().getDeclaredMethods()) {
            // 判断前置增强
            if (method.isAnnotationPresent(Before.class)) {
                Before before = method.getAnnotation(Before.class);
                AspectDefinition ad = createAspectDefinition(bd, method, before);
                ad.setBeforeMethod(method);
            } else if (method.isAnnotationPresent(After.class)) {
                After after = method.getAnnotation(After.class);
                AspectDefinition ad = createAspectDefinition(bd, method, after);
                ad.setAfterMethod(method);
            } else if (method.isAnnotationPresent(AfterReturning.class)) {
                AfterReturning afterReturning = method.getAnnotation(AfterReturning.class);
                AspectDefinition ad = createAspectDefinition(bd, method, afterReturning);
                ad.setAfterReturningMethod(method);
            } else if (method.isAnnotationPresent(AfterThrowing.class)) {
                AfterThrowing afterThrowing = method.getAnnotation(AfterThrowing.class);
                AspectDefinition ad = createAspectDefinition(bd, method, afterThrowing);
                ad.setAfterThrowingMethod(method);
            } else if (method.isAnnotationPresent(Around.class)) {
                Around around = method.getAnnotation(Around.class);
                AspectDefinition ad = createAspectDefinition(bd, method, around);
                ad.setAroundMethod(method);
            }
        }
        // 创建 AspectDefinition 对象, 存入集合 aspectDefinitions
        // 可能有多次添加增强方法
    }

    AspectDefinition createAspectDefinition(BeanDefinition bd, Method method, Annotation annotation) throws Exception {
        Method valueMethod = annotation.getClass().getMethod("value");
        // 提取切点表达式
        String pointcutExpression = (String)valueMethod.invoke(annotation);
        // 判断 切点表达式是否是引用其他方法
        if (pointcutExpression.matches("[\\w$]+\\(\\)")) {
            String poiontcutMethodName = pointcutExpression.substring(0, pointcutExpression.indexOf("("));
            // 通过反射或去方法对象
            Method pointcutMethod = bd.getBeanClass().getDeclaredMethod(poiontcutMethodName);
            Pointcut pointcut = pointcutMethod.getAnnotation(Pointcut.class);
            pointcutExpression = pointcut.value();
        }
        // 从集合获取 AspectDefinition aspectDefinition 对象
        AspectDefinition aspectDefinition = aspectDefinitions.get(pointcutExpression);
        // 集合中没有, 那么创建一个
        if (aspectDefinition == null) {
            // 创建 AspectDefinition 对象, 存入集合 aspectDefinitions
            aspectDefinition = new AspectDefinition();
        }
        // 添加到集合中
        aspectDefinitions.put(pointcutExpression, aspectDefinition);
        aspectDefinition.setPointcutExpression(pointcutExpression);

        if(aspectDefinition.getAspectBean() == null) {
            // 查询切面类的实例
            Object aspectBean = getBean(bd.getBeanClass());
            aspectDefinition.setAspectBean(aspectBean);
        }

        return aspectDefinition;
    }

    public void scanProxyBean(){
        for (Map.Entry<String, Object> entry : this.singletonObjects.entrySet()) {
            String name = entry.getKey();
            Object bean = entry.getValue();
            boolean isProxy = false;
            a: for (Method method : bean.getClass().getDeclaredMethods()) {
                for (AspectDefinition aspectDefinition : aspectDefinitions.values()) {
                    if(aspectDefinition.matches(method)){
                        // 匹配成功
                        isProxy = true;
                        break a;
                    }
                }
            }
            if(isProxy){
                // 生成动态代理对象
                Object proxyBean = createProxyBean(name, bean);
                // 替换目标对象(被代理对象)
                entry.setValue(proxyBean);
            }
        }
    }

    private Object createProxyBean(String name, Object bean) {
        // 生成代理对象
        Class<?>[] interfaces = bean.getClass().getInterfaces();
        if(interfaces == null || interfaces.length == 0){
            // TODO JDK动态代理 扩展实现
            return null;
        } else {
            // CGLIB动态代理
            Enhancer enhancer = new Enhancer();
            enhancer.setSuperclass(bean.getClass());
            enhancer.setCallback(new MethodInterceptor() {
                @Override
                public Object intercept(Object o, Method method, Object[] args,
                                        MethodProxy methodProxy) throws Throwable {
                    // 遍历所有的切面
                    for (AspectDefinition aspectDefinition : aspectDefinitions.values()) {
                        // 判断方法是否被切
                        if(aspectDefinition.matches(method)){
                            // 执行通知 + 业务方法
                            return aspectDefinition.invoke(bean,method, args);
                        }
                    }
                    // 如果没有被切到, 则直接执行业务方法
                    return method.invoke(bean,args);
                }
            });
            return enhancer.create();
        }
    }

}

@Data
class AspectDefinition {
    private String pointcutExpression;
    private Method beforeMethod;
    private Method afterMethod;
    private Method aroundMethod;
    private Method afterReturningMethod;
    private Method afterThrowingMethod;
    private Object aspectBean;

    public boolean matches(Method method) {
        // 方法签名
        String methodString = method.toString();
        String methodRegex = pointcutExpression.substring(
                pointcutExpression.indexOf("(") + 1, pointcutExpression.length() - 1);
        methodRegex = methodRegex.replaceAll("(\\*|\\.{2})", ".*");
        methodRegex = methodRegex.replace("(", "\\(");
        methodRegex = methodRegex.replace(")", "\\)");
        if(methodString.matches(methodRegex)){
            return true;
        }
        return false;
    }
    public Object invoke(Object bean, Method method, Object[] args) {
        try{
            // 执行前置通知
            if(beforeMethod!=null){
                beforeMethod.invoke(aspectBean, new MyJoinPoint(method, args));
            }
            // 执行业务方法
            Object ret = method.invoke(bean, args);
            // 执行返回通知
            if(afterMethod!=null){
                afterReturningMethod.invoke(aspectBean, new MyJoinPoint(method, args));
            }
            return ret;
        } catch (Exception e) {
            // 执行异常通知
            try {
                if(afterThrowingMethod!=null){
                    afterThrowingMethod.invoke(aspectBean, new MyJoinPoint(method, args));
                }
            } catch (Exception ex) {
                throw new RuntimeException(ex);
            }
            throw new RuntimeException(e);
        } finally {
            // 执行后置通知
            try {
                if(aroundMethod!=null){
                    afterMethod.invoke(aspectBean, new MyJoinPoint(method, args));
                }
            } catch (Exception e) {
                throw new RuntimeException(e);
            }
        }
        // 其他通知方法类似实现...
    }
}

class MyJoinPoint implements JoinPoint {

    Method method;
    Object[] args;

    public MyJoinPoint(Method method, Object[] args) {
        this.method = method;
        this.args = args;
    }

    @Override
    public String toShortString() {
        return method.getName();
    }

    @Override
    public String toLongString() {
        return method.getName();
    }

    @Override
    public Object getThis() {
        return method;
    }

    @Override
    public Object getTarget() {
        return null;
    }

    @Override
    public Object[] getArgs() {
        return args;
    }

    @Override
    public Signature getSignature() {
        return null;
    }

    @Override
    public SourceLocation getSourceLocation() {
        return null;
    }

    @Override
    public String getKind() {
        return "";
    }

    @Override
    public StaticPart getStaticPart() {
        return null;
    }

    @Override
    public String toString() {
        return method.toString();
    }
}